﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using Microsoft.CSharp.RuntimeBinder;

using Alicanto.Execution;
using Alicanto.Helpers;
using Alicanto.Lexing;

namespace Alicanto.Compilation.Code.Operators.Primary
{
    internal class OpCall : ICodeGenerator
    {
        public Expression Code { get; private set; }

        public IEnumerable<ParseInstruction> Generate(TokenSequence tokens, Scope scope)
        {
            GetEvaluation evaluation = new GetEvaluation();
            yield return evaluation;

            // Get target
            Expression target = evaluation.Expression;
            if (target.Type == typeof(void))
            {
                target = Expression.Constant(scope.ExecutionContext);
            }

            // Get member
            string member = tokens.Pop(TokenType.SimpleName).Str;

            // Get arguments
            IList<Expression> arguments = new List<Expression>();
            if (tokens.Peek() == Token.BracketOpen)
            {
                ParseSequence sequence = new ParseSequence(Token.BracketOpen, Token.BracketClose);
                yield return sequence;
                arguments = sequence.Expressions;
            }

            Type[] argumentTypes = new Type[arguments.Count];
            for (int i = 0; i < arguments.Count; i++)
                argumentTypes[i] = arguments[i].Type;

            // Resolve method
            MethodInfo method = Reflection.ResolveMethodInfo(target.Type, member, argumentTypes);

            if (method != null)
            {
                this.Code = Expression.Call(
                    target,
                    method,
                    Conversion.EnsureParameterTypes(arguments, Reflection.GetParameterTypes(method))
                );
            }
            else if (target.Type == typeof(ExecutionContext) && _getGlobalMethodInfo(member, argumentTypes) != null)
            {

            }
            else
            {
                // Collect input
                List<Expression> input = new List<Expression>();
                input.Add(target);
                input.AddRange(arguments);

                // Generate argument info
                CSharpArgumentInfo[] argumentInfo = new CSharpArgumentInfo[input.Count];

                for (int i = 0; i < input.Count; i++)
                {
                    argumentInfo[i] = CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null);
                }

                // Create callsite binder
                CallSiteBinder binder = Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(
                    CSharpBinderFlags.ConvertExplicit,
                    member,
                    new Type[] { },
                    Settings.CONTEXT,
                    argumentInfo
                );

                // The call
                this.Code = Expression.Dynamic(
                    binder,
                    typeof(object),
                    input
                );
            }
        }

        private MethodInfo _getGlobalMethodInfo(string name, Type[] argumentTypes)
        {
            return null;
        }
    }
}
